﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Chefbook.DataObjects;
using Chefbook.ViewModels;
using System.Collections.Specialized;
using Chefbook.DataAccess;

namespace UnitTests.ViewModelsTests
{
    [TestFixture]
    public class CategoryViewModelFixture 
    {
        private Category category;
        private CategoryViewModel categoryVM;
        private ChefbookXmlRepository repo;
        private RecipeViewModel recipeVM;
        private Recipe recipe;


        [SetUp]
        public void Init()
        {
            repo = new ChefbookXmlRepository();
            category = new Category(null);
            category.Name = "Foo";
            categoryVM = new CategoryViewModel(category, repo);
        }

        [Test]
        public void SetCategoryModelSetsParentTest()
        {
            var category2 = new Category(category) { Name = "bar" };
            var categoryVM2 = new CategoryViewModel(category2, repo);
            Assert.True(categoryVM2.Parent.Equals(categoryVM));
        }

        [Test]
        public void CreatingCategorySetsSubsProperty()
        {
            var category2 = new Category(category) { Name = "bar" };
            var category3 = new Category(category2) { Name = "baz" };
            var categoryVM2 = new CategoryViewModel(category, repo);
            Assert.True(categoryVM2.Subcategories.Count == 1);
            Assert.True(categoryVM2.Subcategories[0].Subcategories.Count == 1);
        }

        [Test]
        public void CreateCategoryViewModelForCategoryWithTwoRecipes()
        {
            recipe = new Recipe(null);
            var recipeVM = new RecipeViewModel(recipe);

            var cat2 = new Category();
            cat2.AddRecipe(recipe);

            
            var recipe2 = new Recipe(null);
            var recipeVM2 = new RecipeViewModel(recipe2);
            cat2.AddRecipe(recipe2);


            var catVM2 = new CategoryViewModel(cat2);
            Assert.AreEqual(2, catVM2.Recipes.Count);
            Assert.AreEqual(2, catVM2.AllRecipes.Count);
            Assert.AreEqual(2, cat2.DirectRecipes.Count);
            Assert.AreEqual(2, cat2.AllRecipes.Count);
            Assert.Contains(recipeVM, catVM2.Recipes);
            Assert.Contains(recipeVM, catVM2.AllRecipes);
            Assert.Contains(recipeVM2, catVM2.Recipes);
            Assert.Contains(recipeVM2, catVM2.AllRecipes);
        }

        [Test]
        public void CreateCategoryViewModelForCategoryWithASubWithTwoRecipes()
        {
            recipe = new Recipe(null);
            var recipeVM = new RecipeViewModel(recipe);

            var cat = new Category();

            var subCategory = new Category(cat);
            subCategory.AddRecipe(recipe);


            var recipe2 = new Recipe(null);
            var recipeVM2 = new RecipeViewModel(recipe2);
            subCategory.AddRecipe(recipe2);


            var catVM2 = new CategoryViewModel(cat);
            Assert.AreEqual(0, catVM2.Recipes.Count);
            Assert.AreEqual(2, catVM2.AllRecipes.Count);
            Assert.AreEqual(0, cat.DirectRecipes.Count);
            Assert.AreEqual(2, cat.AllRecipes.Count);
            Assert.False(catVM2.Recipes.Contains(recipeVM));
            Assert.Contains(recipeVM, catVM2.AllRecipes);
            Assert.False(catVM2.Recipes.Contains(recipeVM2));
            Assert.Contains(recipeVM2, catVM2.AllRecipes);
        }

        [Test]
        public void CreateCategoryViewModelForCategoryWithTwoRecipesAndRemoveThem()
        {
            recipe = new Recipe(null);
            var recipeVM = new RecipeViewModel(recipe, repo);
            var cat2 = new Category();
            cat2.AddRecipe(recipe);
            var recipe2 = new Recipe(null);
            var recipeVM2 = new RecipeViewModel(recipe2, repo);
            cat2.AddRecipe(recipe2);
            var catVM2 = new CategoryViewModel(cat2, repo);

            cat2.RemoveRecipe(recipe);

            Assert.AreEqual(1, catVM2.Recipes.Count);
            Assert.AreEqual(1, catVM2.AllRecipes.Count);
            Assert.AreEqual(1, cat2.DirectRecipes.Count);
            Assert.AreEqual(1, cat2.AllRecipes.Count);
            Assert.False(catVM2.Recipes.Contains(recipeVM));
            Assert.False(catVM2.AllRecipes.Contains(recipeVM));
            Assert.False(cat2.DirectRecipes.Contains(recipe));
            Assert.False(cat2.AllRecipes.Contains(recipe)); 
            Assert.Contains(recipeVM2, catVM2.Recipes);
            Assert.Contains(recipeVM2, catVM2.AllRecipes);
            Assert.Contains(recipe2, cat2.DirectRecipes);
            Assert.Contains(recipe2, cat2.AllRecipes);

            catVM2.RemoveRecipe(recipeVM2);

            Assert.AreEqual(0, catVM2.Recipes.Count);
            Assert.AreEqual(0, catVM2.AllRecipes.Count);
            Assert.AreEqual(0, cat2.DirectRecipes.Count);
            Assert.AreEqual(0, cat2.AllRecipes.Count);
            Assert.False(catVM2.Recipes.Contains(recipeVM));
            Assert.False(catVM2.AllRecipes.Contains(recipeVM));
            Assert.False(cat2.DirectRecipes.Contains(recipe));
            Assert.False(cat2.AllRecipes.Contains(recipe));
            Assert.False( catVM2.Recipes.Contains(recipeVM2));
            Assert.False(catVM2.AllRecipes.Contains(recipeVM2));
            Assert.False(cat2.DirectRecipes.Contains(recipe2));
            Assert.False(cat2.AllRecipes.Contains(recipe2));
        }

        [Test]
        public void CreateCategoryViewModelForCategoryWithASubWithTwoRecipesAndRemoveThem()
        {
            recipe = new Recipe(null);
            var recipeVM = new RecipeViewModel(recipe);

            var parentCategory = new Category();

            var subCategory = new Category(parentCategory);
            subCategory.AddRecipe(recipe);


            var recipe2 = new Recipe(null);
            var recipeVM2 = new RecipeViewModel(recipe2);
            subCategory.AddRecipe(recipe2);

            var parentVM = new CategoryViewModel(parentCategory);
            var subCategoryVM = new CategoryViewModel(subCategory);

            subCategory.RemoveRecipe(recipe);

            Assert.AreEqual(0, parentVM.Recipes.Count);
            Assert.AreEqual(1, parentVM.AllRecipes.Count);
            Assert.AreEqual(1, subCategory.DirectRecipes.Count);
            Assert.AreEqual(1, subCategory.AllRecipes.Count);
            Assert.False(parentVM.Recipes.Contains(recipeVM));
            Assert.False(parentVM.AllRecipes.Contains(recipeVM));
            Assert.False(subCategory.DirectRecipes.Contains(recipe));
            Assert.False(subCategory.AllRecipes.Contains(recipe));
            Assert.False(parentVM.Recipes.Contains(recipeVM2));
            Assert.Contains(recipeVM2, parentVM.AllRecipes);
            Assert.Contains(recipe2, subCategory.DirectRecipes);
            Assert.Contains(recipe2, subCategory.AllRecipes);

            subCategoryVM.RemoveRecipe(recipeVM2);

            Assert.AreEqual(0, parentVM.Recipes.Count);
            Assert.AreEqual(0, parentVM.AllRecipes.Count);
            Assert.AreEqual(0, subCategory.DirectRecipes.Count);
            Assert.AreEqual(0, subCategory.AllRecipes.Count);
            Assert.False(parentVM.Recipes.Contains(recipeVM));
            Assert.False(parentVM.AllRecipes.Contains(recipeVM));
            Assert.False(subCategory.DirectRecipes.Contains(recipe));
            Assert.False(subCategory.AllRecipes.Contains(recipe));
            Assert.False(parentVM.Recipes.Contains(recipeVM2));
            Assert.False(parentVM.AllRecipes.Contains(recipeVM2));
            Assert.False(subCategory.DirectRecipes.Contains(recipe2));
            Assert.False(subCategory.AllRecipes.Contains(recipe2));
        }

        [Test]
        public void IsSelectedChangeFiresPropertyChanged()
        {
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsSelected = true, "IsSelected");
            Assert.True(categoryVM.IsSelected);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsSelected = true);
            Assert.True(categoryVM.IsSelected);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsSelected = false, "IsSelected");
            Assert.False(categoryVM.IsSelected);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsSelected = false);
            Assert.False(categoryVM.IsSelected);
        }

        [Test]
        public void IsExpandedChangeFiresPropertyChanged()
        {
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsExpanded = true, "IsExpanded");
            Assert.True(categoryVM.IsExpanded);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsExpanded = true);
            Assert.True(categoryVM.IsExpanded);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsExpanded = false, "IsExpanded");
            Assert.False(categoryVM.IsExpanded);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.IsExpanded = false);
            Assert.False(categoryVM.IsExpanded);
        }

        [Test]
        public void CategoryNameChangeFiresPropertyChanged()
        {
            categoryVM.AssertPropertyChangedFired(() => categoryVM.Name = "Hello", "Name");
            Assert.AreEqual("Hello", category.Name);
            Assert.AreEqual("Hello", categoryVM.Name);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.Name = "Hello");
        }

        [Test]
        public void CategoryParentChangeFiresPropertyChanged()
        {
            Category cat2 = new Category(null);
            CategoryViewModel catVM2 = new CategoryViewModel(cat2);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.Parent = catVM2, "Parent");
            Assert.AreEqual(catVM2, categoryVM.Parent);
            Assert.AreEqual(cat2, category.Parent);
            categoryVM.AssertPropertyChangedFired(() => categoryVM.Parent = catVM2);
        }

        [Test]
        public void AddRecipeFiresCollectionChangedOnAllRecipesOfParent()
        {
            recipe = new Recipe(null) { Name = "I am Recipe" };
            recipeVM = new RecipeViewModel(recipe);
            categoryVM.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(
                () => categoryVM.AddRecipe(recipeVM));
            Assert.Contains(recipeVM, categoryVM.AllRecipes);
            Assert.Contains(recipe, category.DirectRecipes, "DirectRecipes");
            Assert.Contains(recipe, category.AllRecipes, "AllRecipes");
            Assert.Contains(categoryVM, recipeVM.Categories);
        }

        [Test]
        public void AllRecipesDoesntContainDuplicatesTest()
        {
            var recipe = ViewModelTestHelper.NewRecipeViewModel(null, repo);
            recipe.Name = "My Recipe";
            var sub = ViewModelTestHelper.NewCategoryViewModel(categoryVM, repo);
            var args = categoryVM.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                sub.AddRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(recipe, args.NewItems);

            var sub2 = ViewModelTestHelper.NewCategoryViewModel(categoryVM, repo);
            sub2.AddRecipe(recipe);

            Assert.AreEqual(2, categoryVM.Subcategories.Count);
            Assert.AreEqual(1, categoryVM.AllRecipes.Count((x) => x.Equals(recipe)), "Recipe wasn't in AllRecipes of the category exactly once");
        }

        [Test]
        public void AddRecipeToSubFiresCollectionChangedOnAllRecipesOfParent()
        {
            var sub = new Category(category);
            var subVM = new CategoryViewModel(sub);
            recipe = new Recipe(null);
            recipeVM = new RecipeViewModel(recipe);
            categoryVM.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(
                () => subVM.AddRecipe(recipeVM));
            Assert.Contains(recipeVM, subVM.Recipes);
            Assert.Contains(subVM, recipeVM.Categories);
        }

        [Test]
        public void AddCategoryFiresCollectionChangedTest()
        {
            var cat = new Category(null);
            CategoryViewModel categoryVM2 = new CategoryViewModel(cat);
            var args = categoryVM.Subcategories.AssertCollectionChangedFiredAndReturnArgs(() => categoryVM.AddSubcategory(categoryVM2));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(categoryVM2, args.NewItems);
        }

        [Test]
        public void RemoveCategoryFiresCollectionChangedTest()
        {
            CategoryViewModel category = new CategoryViewModel(new Category(null));
            categoryVM.AddSubcategory(category);
            var args = categoryVM.Subcategories.AssertCollectionChangedFiredAndReturnArgs(() => categoryVM.RemoveSubcategory(category));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(category, args.OldItems);
        }

    }
}